#include <time.h>
#include <pthread.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
//#include "cwmp/udp.h"
//#include "cwmp/stun.h"
//#include "cwmp/uci_cfg.h"
//#include "cwmp/event.h"
//#include "cwmp_thread.h"
//#include "cwmp_module.h"
#include "udp.h"
#include "stun.h"
#include "cwmp_stun_client.h"

//#ifdef TR069_ANNEX_G
#define	STUN_BINDING_REQ				1
#define STUN_BINDING_MAINTAIN			2
#define STUN_BINDING_TIMEOUT_DISCOVERY	3
#define STUN_BINDING_TIMEOUT_MONITORING	4
#define STUN_BINDING_CHANGE				5
#define STUN_CHANGE_OTHER_PARAM			6
	
#define CWMP_STUN_CNONCE_LEN			256
#define CWMP_STUN_SIGNATURE_LEN			40
#define CWMP_STUN_SIGNATURE_STRING_LEN	2048
	
static int stunThread = 0;
static int stunStop = 0;
static int stunState = STUN_BINDING_REQ;
static int OldStunState = STUN_BINDING_REQ;
	
static pthread_cond_t cond;
static pthread_mutex_t mutex;
	
static char udpConnReqAddr[CWMP_UDP_CONN_REQ_ADDR_LEN+1];
static char stunServerAddr[CWMP_STUN_SERVER_ADDR_LEN+1];
static char OldStunServerAddrAndPort[CWMP_STUN_SERVER_ADDR_LEN+1];
static unsigned int stunServerPort;
static char stunUsername[CWMP_STUN_USERNAME_LEN+1];
static char stunPassword[CWMP_STUN_PASSWORD_LEN+1];
static int stunMaxPeriod;
static unsigned int stunMinPeriod;
static unsigned int natDetected;
static unsigned int msgIntegrityDetected;
//static unsigned int msgIntegrityDetected=1;
	
static char acsUrl[CWMP_ACS_URL_LEN+1];
	
static long long g_ts = 0;
static unsigned int g_id = 0;

//extern cwmp_t *g_cwmp;
//#endif

/*******************************************************************************
Function
*******************************************************************************/
//#ifdef TR069_ANNEX_G

#ifdef AAAAA
void cwmpSendUdpConnReq()
{
	g_cwmp->new_request = CWMP_YES;
	cwmp_event_set_value(g_cwmp, INFORM_CONNECTIONREQUEST, 1, NULL, 0, 0, 0);
}
#endif

static void procUdpConnReq(char *buf, unsigned int bufLen)
{
	int i;
	char *p;
	long long ts = 0; // timestamp
	unsigned int id = 0;
	char un[CWMP_STUN_USERNAME_LEN+1]; // username
	char cn[CWMP_STUN_CNONCE_LEN+1]; // Cnonce
	char sig[CWMP_STUN_SIGNATURE_LEN+1]; // Signature

	char sigStr[CWMP_STUN_SIGNATURE_STRING_LEN];
	char hash[20];
	char hashStr[CWMP_STUN_SIGNATURE_LEN+1];
	
	char connReqUN[CWMP_CONREQ_USERNAME_LEN+1];
	char connReqPW[CWMP_CONREQ_PASSWD_LEN+1];

	cwmp_log_debug("UDP Conn Req: %s", buf);
	cwmp_log_debug("Len (%d)", bufLen);

#if 1  // validation and authentication

	if (buf == NULL)
	{
		cwmp_log_debug("buf NULL error!");
		return;
	}

	if (strncmp(buf, "GET", 3) != 0)
	{
		cwmp_log_debug("not a GET msg!");
		return;
	}

	if (strstr(buf, "HTTP/1.1") == NULL)
	{
		cwmp_log_debug("not a HTTP/1.1 msg!");
		return;
	}

	p = strtok(buf, "?=& ");

	while (p != NULL)
	{
		cwmp_log_debug("strtok (%s)", p);

		if (strcmp(p, "ts") == 0)
		{
			p = strtok(NULL, "?=& ");

			if (p != NULL)
			{
				ts = atoll(p);
				cwmp_log_debug("ts (%lld)", ts);
			}
		}
		else if (strcmp(p, "id") == 0)
		{
			p = strtok(NULL, "?=& ");

			if (p != NULL)
			{
				id = atoi(p);
				cwmp_log_debug("id (%u)", id);
			}
		}
		else if (strcmp(p, "un") == 0)
		{
			p = strtok(NULL, "?=& ");

			if (p != NULL)
			{
				strcpy(un, p);
				cwmp_log_debug("un (%s)", un);
			}
		}
		else if (strcmp(p, "cn") == 0)
		{
			p = strtok(NULL, "?=& ");

			if (p != NULL)
			{
				strcpy(cn, p);
				cwmp_log_debug("cn (%s)", cn);
			}
		}
		else if (strcmp(p, "sig") == 0)
		{
			p = strtok(NULL, "?=& ");

			if (p != NULL)
			{
				strcpy(sig, p);
				cwmp_log_debug("sig (%s)", sig);
			}
		}

		p = strtok(NULL, "?=& ");
	}

	if (ts <= g_ts)
	{
		cwmp_log_debug("ts(%lld) <= g_ts(%lld) error!", ts, g_ts);
		return;
	}
	else
	{
		g_ts = ts;
	}

	if (id == g_id)
	{
		cwmp_log_debug("id(%u) == g_id(%u) error!", id, g_id);
		return;
	}
	else
	{
		g_id = id;
	}

	//cwmp_uci_get("netcwmp.@local[0].username", connReqUN);
	strcpy(connReqUN, STUN_USERNAME);

	if (strcmp(un, connReqUN) != 0)
	{
		cwmp_log_debug("username (un) mismatch error!");
		return;
	}

	//cwmp_uci_get("netcwmp.@local[0].password", connReqPW);
	strcpy(connReqPW, STUN_PASSWORD);

	sprintf(sigStr, "%lld%u%s%s", ts, id, un, cn);

	cwmp_log_debug("sigStr (%s)", sigStr);

    /* use sigStr with connReqPW to generate Hmac for comparison. */
#ifdef HAS_MESSAGE_INTEGRITY
	computeHmac(hash, sigStr, strlen(sigStr), connReqPW, strlen(connReqPW));
	toHex(hash, 20, hashStr);
	hashStr[CWMP_STUN_SIGNATURE_LEN] = 0;
	if(strncasecmp(sig, hashStr, CWMP_STUN_SIGNATURE_LEN) !=0 )
	{
		cwmp_log_debug("signature (sig) mismatch error!, server sig(%s), client sig(%s)",sig, hashStr);
		return;
	}
#else
//#error "please set openssl-0.9.8i!"
#endif

#endif

#ifdef AAAAA
	cwmpSendUdpConnReq();
#endif
}

static void stun_getHost(char *host, char *url)
{ 
	const char *s;
	int i, n;

	if (!url || !*url)
		return;

	s = strchr(url, ':');
	if (s && s[1] == '/' && s[2] == '/')
		s += 3;
	else
		s = url;

	cwmp_log_debug("s (%s)", s);

	n = strlen(s);

	for (i = 0; i < n; i++)
	{ 
		host[i] = s[i];
		if (s[i] == '/' || s[i] == ':')
			break; 
	}

	host[i] = '\0';
	return;
}

static int stun_getHostByName(char *addr, struct in_addr *inaddr)
{ 
	in_addr_t iadd = -1;
	struct hostent hostent, *host = &hostent;

	iadd = inet_addr(addr);

	if (iadd != -1)
	{ 
		memcpy(inaddr, &iadd, sizeof(iadd));
		return 0;
	}

	host = gethostbyname(addr);

	if (!host)
	{ 
		cwmp_log_debug("Host name not found");
		return -1;
	}

	memcpy(inaddr, host->h_addr, host->h_length);

	return 0;
}

static void *stun_thread()
{
	StunAddress4 stunServer, stunMappedAddr;
	StunMessage stunMsg;
	Socket priFd = -1, secFd = -1, s = -1;
	struct in_addr in;
	char checkAddr[CWMP_UDP_CONN_REQ_ADDR_LEN+1];
	char checkStunServerAddrAndPort[CWMP_UDP_CONN_REQ_ADDR_LEN+1];
	char host[CWMP_ACS_URL_LEN+1];
	unsigned int checkNat;
	unsigned int stunPeriod;
	struct timeval now;
	struct timespec outtime;
	time_t now_time_t, out_time_t;

	cwmp_log_debug("stun started!!!");
	//sleep(1); //wait for set value to mib

	pthread_mutex_lock(&mutex);

stun_reread:
	stunState = STUN_BINDING_REQ;

	//cwmp_uci_get("netcwmp.@acs[0].url", acsUrl);
	//cwmp_uci_get("netcwmp.@local[0].stun_server_address", stunServerAddr);
	//cwmp_uci_get("netcwmp.@local[0].stun_username", stunUsername);
	//cwmp_uci_get("netcwmp.@local[0].stun_password", stunPassword);
	//stunServerPort = cwmp_uci_get_int("netcwmp.@local[0].stun_server_port");
	//stunMinPeriod = cwmp_uci_get_int("netcwmp.@local[0].stun_min_keep_alive_period");
	//stunMaxPeriod = cwmp_uci_get_int("netcwmp.@local[0].stun_max_keep_alive_period");
	strcpy(acsUrl,STUN_URL);
	strcpy(stunServerAddr,STUN_SERVER_ADDR);
	strcpy(stunUsername,STUN_USERNAME);
	strcpy(stunPassword,STUN_PASSWORD);
	stunServerPort = STUN_SERVER_PORT;
	stunMinPeriod = STUN_MIN_PERIOD;
	stunMaxPeriod = STUN_MAX_PERIOD;

	cwmp_log_debug("stunServerAddr (%s)", stunServerAddr);
	cwmp_log_debug("stunServerPort (%d)", stunServerPort);
	cwmp_log_debug("stunUsername (%s)", stunUsername);
	cwmp_log_debug("stunPassword (%s)", stunPassword);
	cwmp_log_debug("stunMaxPeriod (%d)", stunMaxPeriod);
	cwmp_log_debug("stunMinPeriod (%d)", stunMinPeriod);
	cwmp_log_debug("acsUrl (%s)", acsUrl);

	memset(&stunServer, 0, sizeof(StunAddress4));

	if (strlen(stunServerAddr) == 0)
	{
		stun_getHost(host, acsUrl);
	}
	else
	{
		stun_getHost(host, stunServerAddr);
	}

	cwmp_log_debug("host %s", host);

	while(1)
	{
		if (stun_getHostByName(host, &in) == -1)
			sleep(5);
		else
			break;
		//goto stun_err;
	}
	
	stunServer.addr = (UInt32)in.s_addr;

	if (stunServerPort == 0)
	{
		stunServer.port = STUN_PORT; // use default stun port number
	}
	else
	{
		stunServer.port = stunServerPort;
	}

	//in.s_addr = htonl(stunServer.addr);
	in.s_addr = stunServer.addr;
	cwmp_log_debug("stun server: %s:%d", inet_ntoa(in), stunServer.port);
	sprintf(OldStunServerAddrAndPort, "%s:%d", inet_ntoa(in), stunServer.port);
	cwmp_log_debug("bobtrace %s %d", __FUNCTION__, __LINE__);

	while (!stunStop)
	{
		cwmp_log_debug("stunState (%d), msgIntegrityDetected (%d)", stunState, msgIntegrityDetected);

		memset(&stunMsg, 0, sizeof(StunMessage));
		
		switch (stunState)
		{
			case STUN_BINDING_REQ:
				FUNCTION_TRACE();

				priFd = openPort(0, 0);

				stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 0, 0, NULL, stunUsername, NULL, procUdpConnReq);
				//stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);
				if (stunMsg.hasErrorCode && stunMsg.errorCode.errorClass == 4 &&
							stunMsg.errorCode.number == 1) // Unauthorized
				{
					msgIntegrityDetected = 1;
					stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);

					if (stunMsg.hasErrorCode)
					{
						cwmp_log_debug("Unauthorized Username/Password");
						close(priFd);
						priFd = -1;
						break;
					}
				}
				else
				{
					msgIntegrityDetected = 0;
				}
				cwmp_log_debug("msgIntegrityDetected = %u", msgIntegrityDetected);

				if (stunMsg.hasMappedAddress)
				{
					s = openPort(11000, stunMsg.mappedAddress.ipv4.addr);

					if (s != INVALID_SOCKET) // not behind NAT
					{
						close(s);
						s = -1;
						natDetected = 0;
					}
					else
					{
						natDetected = 1;
					}

					stunMappedAddr.addr = stunMsg.mappedAddress.ipv4.addr;
					stunMappedAddr.port = stunMsg.mappedAddress.ipv4.port;

					in.s_addr = htonl(stunMsg.mappedAddress.ipv4.addr);
					sprintf(udpConnReqAddr, "%s:%d", inet_ntoa(in),
						stunMsg.mappedAddress.ipv4.port);

					//cwmp_uci_set("netcwmp.@local[0].udp_connreq_address", udpConnReqAddr);
					//cwmp_uci_set_int("netcwmp.@local[0].nat_detected", natDetected);

					cwmp_log_debug("isNAT: %d, UDPConnReqAddr: %s", natDetected, udpConnReqAddr);

					if (natDetected)
					{
						/* Binding Req with BindingChange attr. */
						if(msgIntegrityDetected == 1)
							stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);
						else
							stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, NULL, procUdpConnReq);

#ifdef _CWMP_WITH_SSL_
						if (stunMsg.hasErrorCode && stunMsg.errorCode.errorClass == 4 &&
							stunMsg.errorCode.number == 1) // Unauthorized
						{
							msgIntegrityDetected = 1;
							stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);

							if (stunMsg.hasErrorCode)
							{
								cwmp_log_debug("Unauthorized Username/Password");
								close(priFd);
								break;
							}
						}
						else
						{
							//msgIntegrityDetected = 0;
						}
#endif

						if (stunMaxPeriod == stunMinPeriod)
						{
							stunPeriod = stunMinPeriod;
							stunState = STUN_BINDING_MAINTAIN;
						}
						else
						{
							stunPeriod = (stunMinPeriod + stunMaxPeriod) / 2;
							if(stunPeriod == stunMinPeriod || stunPeriod == stunMaxPeriod)
								stunState = STUN_BINDING_MAINTAIN;
							else
								stunState = STUN_BINDING_TIMEOUT_DISCOVERY;
						}

						cwmp_log_debug("stunState (%d) stunPeriod (%d) "
							"stunMinPeriod (%d) stunMaxPeriod (%d)",
							stunState, stunPeriod, stunMinPeriod, stunMaxPeriod);
					}
					else
					{
						FUNCTION_TRACE();
						close(priFd);
						priFd = -1;
						gettimeofday(&now, NULL);
						outtime.tv_sec = now.tv_sec + stunMaxPeriod;
						outtime.tv_nsec = now.tv_usec * 1000;
						pthread_cond_timedwait(&cond, &mutex, &outtime);
						//sleep(stunMaxPeriod);
					}
				}
				else
				{
					FUNCTION_TRACE();
					close(priFd);
					priFd = -1;
					gettimeofday(&now, NULL);
					//outtime.tv_sec = now.tv_sec + stunMaxPeriod;
					outtime.tv_sec = now.tv_sec + 10;
					outtime.tv_nsec = now.tv_usec * 1000;
					pthread_cond_timedwait(&cond, &mutex, &outtime);
					goto stun_reread;
					//sleep(stunMaxPeriod);
				}
				break;

			case STUN_BINDING_MAINTAIN:
				pthread_mutex_unlock(&mutex);
				now_time_t = time(NULL);
				out_time_t = now_time_t + stunPeriod;
				while(now_time_t < out_time_t)
				{
					cwmp_log_debug("now_time_t = %d, out_time_t = %d\n", now_time_t, out_time_t);
					stunTest_tr111_recv(priFd, 1, &stunMsg, procUdpConnReq);
					if(stunState != STUN_BINDING_MAINTAIN)
					{
						pthread_mutex_lock(&mutex);
						goto stun_binding_maintain_break;
					}
					now_time_t = time(NULL);
					if(out_time_t - now_time_t > stunPeriod) // in case ntp update time to a value much older than last time during while
					{
						cwmp_log_debug("now_time_t = %d, out_time_t = %d, stunPeriod = %d, ntp updated the time, break\n", now_time_t, out_time_t, stunPeriod);
						break;
					}
				}
				pthread_mutex_lock(&mutex);
				//gettimeofday(&now, NULL);
				//outtime.tv_sec = now.tv_sec + stunPeriod;
				//outtime.tv_nsec = now.tv_usec * 1000;
				//pthread_cond_timedwait(&cond, &mutex, &outtime);
				//sleep(stunPeriod);
				
				/* jump to STUN_BINDING_REQ if local IP changed */

				if(msgIntegrityDetected == 1)
					stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 0, 0, NULL, stunUsername, stunPassword, procUdpConnReq);
				else
					stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 0, 0, NULL, stunUsername, NULL, procUdpConnReq);

				if (stunMsg.hasMappedAddress)
				{
					s = openPort(11000, stunMsg.mappedAddress.ipv4.addr);

					if (s != INVALID_SOCKET) // not behind NAT
					{
						close(s);
						s = -1;
						checkNat = 0;
					}
					else
					{
						checkNat = 1;
					}

					in.s_addr = htonl(stunMsg.mappedAddress.ipv4.addr);
					sprintf(checkAddr, "%s:%d", inet_ntoa(in),
						stunMsg.mappedAddress.ipv4.port);

					cwmp_log_debug("isNAT: %d, UDPConnReqAddr: %s", checkNat, checkAddr);

					if (checkNat != natDetected)
					{
						cwmp_log_debug("natDetected changed!!!");
						//cwmp_uci_set_uint("netcwmp.@local[0].nat_detected", checkNat);
						natDetected = checkNat;
					}

					if (strcmp(checkAddr, udpConnReqAddr) != 0)
					{
						cwmp_log_debug("UDPConnReqAddr changed!!!");
						//cwmp_uci_set("netcwmp.@local[0].udp_connreq_address", checkAddr);
						strcpy(udpConnReqAddr, checkAddr);

						/* do sometheing according to G.2.1.3 */
						if (natDetected)
						{
							/* Binding Req with BindingChange attr. */
							if(msgIntegrityDetected == 1)
								stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);
							else
								stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, NULL, procUdpConnReq);

#ifdef _CWMP_WITH_SSL_
							if (stunMsg.hasErrorCode && stunMsg.errorCode.errorClass == 4 &&
								stunMsg.errorCode.number == 1) // Unauthorized
							{
								stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);

								if (stunMsg.hasErrorCode)
								{
									cwmp_log_debug("Unauthorized Username/Password");
									stunState = STUN_BINDING_REQ;
									close(priFd);
									break;
								}
							}
#endif
						}
					}
				}
				else
				{
					FUNCTION_TRACE();
					stunState = STUN_BINDING_REQ;
					close(priFd);
					priFd = -1;
					gettimeofday(&now, NULL);
					outtime.tv_sec = now.tv_sec + stunMaxPeriod;
					outtime.tv_nsec = now.tv_usec * 1000;
					pthread_cond_timedwait(&cond, &mutex, &outtime);
					//sleep(stunMaxPeriod);
				}
stun_binding_maintain_break:
				break;

			case STUN_BINDING_TIMEOUT_DISCOVERY:
				gettimeofday(&now, NULL);
				outtime.tv_sec = now.tv_sec + stunPeriod;
				outtime.tv_nsec = now.tv_usec * 1000;
				pthread_cond_timedwait(&cond, &mutex, &outtime);
				//sleep(stunPeriod);

				secFd = openPort(stunMappedAddr.port+1, 0);

				if(msgIntegrityDetected == 1)
					stunTest_tr111_send(secFd, &stunServer, 1, 1, 0, 0, 1, &stunMappedAddr, stunUsername, stunPassword);
				else
					stunTest_tr111_send(secFd, &stunServer, 1, 1, 0, 0, 1, &stunMappedAddr, stunUsername, NULL);

				stunTest_tr111_recv(priFd, 1, &stunMsg, procUdpConnReq);

				if (stunMsg.hasMappedAddress) // not timeout yet
				{
					cwmp_log_debug("not timeout yet, stunPeriod (%d)", stunPeriod);
					//stunPeriod *= 2;
					stunMinPeriod = stunPeriod;
					stunPeriod = (stunMinPeriod + stunMaxPeriod)/2;
					if(stunPeriod == stunMinPeriod || stunPeriod == stunMaxPeriod)
						stunState = STUN_BINDING_MAINTAIN;
					cwmp_log_debug("stunPeriod = (%d)", stunPeriod);
				}
				else // already timeout
				{
					cwmp_log_debug("already timeout, stunPeriod (%d)", stunPeriod);
					//stunPeriod /= 2;
					stunMaxPeriod = stunPeriod;
					stunPeriod = (stunMinPeriod + stunMaxPeriod)/2;
					cwmp_log_debug("stunPeriod = (%d)", stunPeriod);
					stunState = STUN_BINDING_TIMEOUT_MONITORING;
				}

				close(secFd);
				secFd = -1;
				break;

			case STUN_BINDING_TIMEOUT_MONITORING:
				if(msgIntegrityDetected == 1)
					stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 0, 0, NULL, stunUsername, stunPassword, procUdpConnReq);
				else
					stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 0, 0, NULL, stunUsername, NULL, procUdpConnReq);

				if (stunMsg.hasMappedAddress)
				{
					s = openPort(11000, stunMsg.mappedAddress.ipv4.addr);

					if (s != INVALID_SOCKET) // not behind NAT
					{
						close(s);
						s = -1;
						checkNat = 0;
					}
					else
					{
						checkNat = 1;
					}

					stunMappedAddr.addr = stunMsg.mappedAddress.ipv4.addr;
					stunMappedAddr.port = stunMsg.mappedAddress.ipv4.port;

					in.s_addr = htonl(stunMsg.mappedAddress.ipv4.addr);
					sprintf(checkAddr, "%s:%d", inet_ntoa(in),
						stunMsg.mappedAddress.ipv4.port);

					cwmp_log_debug("isNAT: %d, UDPConnReqAddr: %s", checkNat, checkAddr);

					if (checkNat != natDetected)
					{
						cwmp_log_debug("natDetected changed!!!");
						//cwmp_uci_set_uint("netcwmp.@local[0].nat_detected", checkNat);
						natDetected = checkNat;
					}

					if (strcmp(checkAddr, udpConnReqAddr) != 0)
					{
						cwmp_log_debug("UDPConnReqAddr changed!!!");
						//cwmp_uci_set("netcwmp.@local[0].udp_connreq_address", checkAddr);
						strcpy(udpConnReqAddr, checkAddr);

						/* do sometheing according to G.2.1.3 */
						if (natDetected)
						{
							/* Binding Req with BindingChange attr. */
							if(msgIntegrityDetected == 1)
								stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);
							else
								stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, NULL, procUdpConnReq);

#ifdef _CWMP_WITH_SSL_
							if (stunMsg.hasErrorCode && stunMsg.errorCode.errorClass == 4 &&
								stunMsg.errorCode.number == 1) // Unauthorized
							{
								stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);

								if (stunMsg.hasErrorCode)
								{
									cwmp_log_debug("Unauthorized Username/Password (%d-%d)",
										         stunMsg.errorCode.errorClass,
										         stunMsg.errorCode.number);
									
									stunState = STUN_BINDING_REQ;
									close(priFd);
									break;
								}
							}
#endif
						}
					}


					if (!natDetected)
					{
						FUNCTION_TRACE();
						gettimeofday(&now, NULL);
						outtime.tv_sec = now.tv_sec + stunPeriod;
						outtime.tv_nsec = now.tv_usec * 1000;
						pthread_cond_timedwait(&cond, &mutex, &outtime);
						//sleep(stunPeriod);
						break;
					}
				}
				else
				{
					FUNCTION_TRACE();
					gettimeofday(&now, NULL);
					outtime.tv_sec = now.tv_sec + stunPeriod;
					outtime.tv_nsec = now.tv_usec * 1000;
					pthread_cond_timedwait(&cond, &mutex, &outtime);
					//sleep(stunPeriod);
					break;
				}

				cwmp_log_debug("stunPeriod (%d)", stunPeriod);

				gettimeofday(&now, NULL);
				outtime.tv_sec = now.tv_sec + stunPeriod;
				outtime.tv_nsec = now.tv_usec * 1000;
				pthread_cond_timedwait(&cond, &mutex, &outtime);
				//sleep(stunPeriod);

				memset(&stunMsg, 0, sizeof(StunMessage));

				secFd = openPort(stunMappedAddr.port+1, 0);

				if(msgIntegrityDetected == 1)
					stunTest_tr111_send(secFd, &stunServer, 1, 1, 0, 0, 1, &stunMappedAddr, stunUsername, stunPassword);
				else
					stunTest_tr111_send(secFd, &stunServer, 1, 1, 0, 0, 1, &stunMappedAddr, stunUsername, NULL);

				stunTest_tr111_recv(priFd, 1, &stunMsg, procUdpConnReq);

				close(secFd);
				secFd = -1;

				if (stunMsg.hasMappedAddress) // not timeout yet
				{
					cwmp_log_debug("not timeout yet");

					stunState = STUN_BINDING_MAINTAIN;
				}
				else // already timeout
				{
					cwmp_log_debug("already timeout");

					close(priFd);
					priFd = -1;
					
					stunMaxPeriod = stunPeriod;
					stunState = STUN_BINDING_REQ;
				}
				break;

			case STUN_BINDING_CHANGE:
				//cwmp_uci_get("netcwmp.@local[0].stun_server_address", stunServerAddr);
				//stunServerPort = cwmp_uci_get_uint("netcwmp.@local[0].stun_server_port");
				strcpy(stunServerAddr, STUN_SERVER_ADDR);
				stunServerPort = STUN_SERVER_PORT;

				cwmp_log_debug("stunServerAddr (%s)", stunServerAddr);
				cwmp_log_debug("stunServerPort (%d)", stunServerPort);

				memset(&stunServer, 0, sizeof(StunAddress4));

				if (strlen(stunServerAddr) == 0)
				{
					stun_getHost(host, acsUrl);
				}
				else
				{
					stun_getHost(host, stunServerAddr);
				}

				cwmp_log_debug("host %s", host);
					
				if (stun_getHostByName(host, &in) == -1)
					goto stun_err;
				
				stunServer.addr = (UInt32)in.s_addr;

				if (stunServerPort == 0)
				{
					stunServer.port = STUN_PORT; // use default stun port number
				}
				else
				{
					stunServer.port = stunServerPort;
				}

				//in.s_addr = htonl(stunServer.addr);
				in.s_addr = stunServer.addr;
				cwmp_log_debug("stun server: %s:%d", inet_ntoa(in), stunServer.port);
				sprintf(checkStunServerAddrAndPort, "%s:%d", inet_ntoa(in), stunServer.port);

				if (strcmp(checkStunServerAddrAndPort, OldStunServerAddrAndPort) != 0)
				{
					close(priFd);
					priFd = -1;
					cwmp_log_debug("Stun Server Address or port changed!!!");
					strcpy(OldStunServerAddrAndPort, checkStunServerAddrAndPort);

					priFd = openPort(0, 0);

					stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, NULL, procUdpConnReq);
					if (stunMsg.hasErrorCode && stunMsg.errorCode.errorClass == 4 &&
								stunMsg.errorCode.number == 1) // Unauthorized
					{
						msgIntegrityDetected = 1;
						stunTest_tr111(priFd, &stunServer, 1, 1, &stunMsg, 1, 1, 0, NULL, stunUsername, stunPassword, procUdpConnReq);

						if (stunMsg.hasErrorCode)
						{
							cwmp_log_debug("Unauthorized Username/Password");
							close(priFd);
							priFd = -1;
							break;
						}
					}
					else
					{
						msgIntegrityDetected = 0;
					}

					if (stunMsg.hasMappedAddress)
					{
						s = openPort(11000, stunMsg.mappedAddress.ipv4.addr);

						if (s != INVALID_SOCKET) // not behind NAT
						{
							close(s);
							s = -1;
							natDetected = 0;
						}
						else
						{
							natDetected = 1;
						}

						stunMappedAddr.addr = stunMsg.mappedAddress.ipv4.addr;
						stunMappedAddr.port = stunMsg.mappedAddress.ipv4.port;

						in.s_addr = htonl(stunMsg.mappedAddress.ipv4.addr);
						sprintf(udpConnReqAddr, "%s:%d", inet_ntoa(in),
							stunMsg.mappedAddress.ipv4.port);

						//cwmp_uci_set("netcwmp.@local[0].udp_connreq_address", udpConnReqAddr);
						//cwmp_uci_set_int("netcwmp.@local[0].nat_detected", natDetected);

						cwmp_log_debug("isNAT: %d, UDPConnReqAddr: %s", natDetected, udpConnReqAddr);

						if (natDetected)
						{
							if (stunMaxPeriod == stunMinPeriod)
							{
								stunPeriod = stunMinPeriod;
								stunState = STUN_BINDING_MAINTAIN;
							}
							else
							{
								stunPeriod = (stunMinPeriod + stunMaxPeriod) / 2;
								if(stunPeriod == stunMinPeriod || stunPeriod == stunMaxPeriod)
									stunState = STUN_BINDING_MAINTAIN;
								else
									stunState = STUN_BINDING_TIMEOUT_DISCOVERY;
							}

							cwmp_log_debug("stunState (%d) stunPeriod (%d) "
								"stunMinPeriod (%d) stunMaxPeriod (%d)",
								stunState, stunPeriod, stunMinPeriod, stunMaxPeriod);
						}
						else
						{
							FUNCTION_TRACE();
							close(priFd);
							priFd = -1;
							stunState = STUN_BINDING_REQ;
							gettimeofday(&now, NULL);
							outtime.tv_sec = now.tv_sec + stunMaxPeriod;
							outtime.tv_nsec = now.tv_usec * 1000;
							pthread_cond_timedwait(&cond, &mutex, &outtime);
							//sleep(stunMaxPeriod);
						}
					}
					else
					{
						FUNCTION_TRACE();
						close(priFd);
						priFd = -1;
						stunState = STUN_BINDING_REQ;
						gettimeofday(&now, NULL);
						outtime.tv_sec = now.tv_sec + stunMaxPeriod;
						outtime.tv_nsec = now.tv_usec * 1000;
						pthread_cond_timedwait(&cond, &mutex, &outtime);
						//sleep(stunMaxPeriod);
					}
				}
				else
				{
					FUNCTION_TRACE();
					stunState = OldStunState;
				}
				break;

			case STUN_CHANGE_OTHER_PARAM:
				goto stun_reread;

			default:
				break;
		}
	}

stun_err:

	if(priFd != -1)
		close(priFd);
	if(secFd != -1)
		close(secFd);
	if(s != -1)
		close(s);

	cwmp_log_debug("stun stopped!!!");

	stunThread = 0;
	pthread_mutex_unlock(&mutex);
}

void cwmp_start_stun()
{
	pthread_t stun_pid;
	
	FUNCTION_TRACE();
	
	if (stunThread)
	{
		cwmp_log_debug("stun already in progress!!!");
		return;
	}

	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);

	stunThread = 1;
	stunStop = 0;
	if( pthread_create( &stun_pid, NULL, stun_thread, 0 ) != 0 )
	{
		cwmp_log_debug("stun thread create fail!!!");
		stunThread = 0;
		return;
	}
	
	pthread_detach(stun_pid);
}

void cwmp_stop_stun()
{
	FUNCTION_TRACE();

	pthread_mutex_lock(&mutex);
	stunStop = 1;
	pthread_cond_signal(&cond);
	pthread_mutex_unlock(&mutex);
	
}

void cwmp_encode_test()
{
	cwmp_start_stun();		
}

int main()
{
	cwmp_encode_test();	
	msgIntegrityDetected = 1;
	while(1)
	{
		sleep(2);
	}
	return 0;
}
//#endif
